using System;
using System.Collections.Generic;
using System.Xml;
using System.Data;
using System.Diagnostics;
using Adoor.Expression;
using Adoor.Object;
using Adoor.Object.Entity;
using Adoor.Relational;
using Adoor.Data;  // For SmartKey
using Adoor.Object.Domain;

namespace Adoor.Object.Relational
{
    public class SmartKeyFactory<TEntity> : SmartKeyMapping<TEntity>, IOidFactory<TEntity>
    {
        public const int MaskLength = 24;
        private RelationalObjectSource _relationnalObjectSource = null;


        public SmartKeyFactory(Adoor.Object.Domain.EntityData entityData, XmlNode metadata)
            : base(entityData, metadata)
        {
            //this._relationnalObjectSource = relationalObjectSource;
            _relationnalObjectSource = (RelationalObjectSource)this.EntityData.Domain.DomainGroup.RemoteSource;
        
            try
            {
                this.mask = Convert.ToInt32(this.EntityData.ClassId) << MaskLength;
            }
            catch (Exception e)
            {
                throw new Exception("bad or missing @classId attribute for type " + this.EntityData.EntityType.Name, e);
            }

            XmlNode oidRangeNode = metadata.SelectSingleNode("@oidRange");
            if (oidRangeNode != null)
                this.oidRange = Convert.ToInt32(oidRangeNode.Value);
            else if (this.EntityData.ExtendedProperties["OidRange"] != null)
                this.oidRange = Convert.ToInt32(this.EntityData.ExtendedProperties["OidRange"]);
            else
                this.oidRange = 1;

            XmlNode sqlNode = metadata.SelectSingleNode("@sqlStatement");
            if (sqlNode == null)
                throw new Exception("@sqlStatemet node not found in mapping file");
            
            Adoor.Object.Domain.EntityData parentData = this.EntityData.ParentData;
            while (parentData != null)
            {
                parentData.PolymorphismManager.AddOidData(this);
                parentData = parentData.ParentData;
            }

            if (this.EntityData.PolymorphismManager != null)
                this.EntityData.PolymorphismManager.AddOidData(this);
        }

        public IOidData OidData
        {
            get { return this; }
        }

        public object Discriminator
        {
            get { return this.mask >> MaskLength; }
        }

        public void CreateKey(IEntityState state)
        {
            ((IRelationalEntityState)state)[this.PrimaryKey] = this.GetNextOid() + mask;
        }

        private int GetNextOid()
        {
            IDbConnectionSupplier source = this._relationnalObjectSource.RelationalSource as Adoor.Data.IDbConnectionSupplier ;
            
            if (this.nextOid >= this.maxOid)
            {
                using (IDbConnection connection = source.DbConnection)
                {
                    IDbCommand command = connection.CreateCommand();
                    command.CommandTimeout = 0;
                    command.CommandText = "ITSEQ_NEXT_VAL";
                    command.CommandType = CommandType.StoredProcedure;

                    IDbDataParameter seq_id = command.CreateParameter();
                    seq_id.ParameterName = "@seqid";
                    seq_id.DbType = DbType.AnsiString;
                    seq_id.Value = this.EntityData.EntityType.Name;
                    command.Parameters.Add(seq_id);

                    IDbDataParameter ident_incr = command.CreateParameter();
                    ident_incr.ParameterName = "@ident_incr";
                    ident_incr.DbType = DbType.Int32;
                    ident_incr.Value = this.oidRange;
                    command.Parameters.Add(ident_incr);

                    IDbDataParameter result = command.CreateParameter();
                    result.ParameterName = "@result";
                    result.DbType = DbType.Int32;
                    result.Direction = ParameterDirection.Output;
                    command.Parameters.Add(result);

                    //System.Diagnostics.Trace.WriteLine(command.CommandText + "(" + this.EntityData.EntityType.Name + ", " + this.oidRange + ")", "KEY");
                    connection.Open();
                    command.ExecuteNonQuery();
                    this.nextOid = Convert.ToInt32(result.Value);
                    this.maxOid = this.nextOid + this.oidRange;
                }
            }
            return this.nextOid++;
        }

        private int oidRange;
        private int mask;

        private int nextOid;
        private int maxOid;

       
    }

}
